RAG 비법노트 기본편 — 딥다이브 학습 노트
학습 로드맵
[EP1 RAG 개념] → [EP2 임베딩·질의분해] → [EP3 랭체인·파이프라이닝]
↓
[EP4 프롬프트 기법] → [EP5 모델·튜닝] → [EP6 컨텍스트·외부데이터]
↓
[EP7 LLM vs RAG 임베딩] → [EP8 임베딩 모델] → [EP9 희소·밀집 벡터]
선수 지식 (RAG 특화)
- [ ] 벡터/행렬 기초 개념 (코사인 유사도, 내적 계산)
- [ ] LLM 기본 사용 경험 (ChatGPT, Claude API 호출)
추천 학습 순서
- 1회차: EP1~3 순서대로 (개념 → 임베딩 → 도구)
- 2회차: EP4~6 (프롬프트 → 모델 → 데이터)
- 3회차: EP7~9 (임베딩 심화 → 모델 선택 → 벡터 종류)
- 복습: 플래시카드로 핵심 개념 셀프 테스트
EP1. RAG가 뭐야?
강의 핵심 요약
RAG = Retrieval-Augmented Generation - Retrieval (검색): 벡터 DB에서 유사한 정보를 찾아옴 - Augmented (강화): 검색된 정보로 프롬프트의 컨텍스트를 강화 - Generation (생성): LLM이 강화된 컨텍스트를 기반으로 답변 생성
핵심 인사이트: RAG는 사실상 RAC (Retrieval-Augmented Context) 에 가깝다. 생성을 강화하는 게 아니라, 컨텍스트를 강화하는 것이다.
중요: LLM과 리트리버는 완전히 독립적인 기술이다. - 리트리버(벡터 검색)는 별도 머신에서 운영 가능 - 임베딩, DB 검색, 컨텍스트 강화 모두 LLM과 무관 - LLM은 최종 단계에서 강화된 컨텍스트를 받아 답변만 생성
딥다이브: RAG 아키텍처 전체 파이프라인
┌─────────────────────────────────────────────────────────┐
│ RAG 파이프라인 │
│ │
│ [사용자 질의] ──→ [임베딩] ──→ [벡터 검색] ──→ [리랭킹] │
│ ↓ │
│ [관련 문서 Top-K] │
│ ↓ │
│ [시스템 프롬프트] + [컨텍스트(검색결과)] + [사용자 질의] │
│ ↓ │
│ [LLM 생성] │
│ ↓ │
│ [최종 답변] │
└─────────────────────────────────────────────────────────┘
인덱싱 (Indexing) — 사전 준비 단계
[원본 문서] → [청킹(분할)] → [임베딩] → [벡터 DB 저장]
PDF Chunk 1 [0.12, -0.34, ...] Chroma
HTML Chunk 2 [0.56, 0.78, ...] Pinecone
TXT Chunk 3 ... Weaviate
RAG의 세 가지 세대
| 구분 | Naive RAG | Advanced RAG | Modular RAG | Agentic RAG |
|---|---|---|---|---|
| 검색 | 단순 벡터 유사도 | 질의분해 + 리랭킹 | 플러그인 방식 모듈 조합 | 에이전트가 검색 필요 여부 판단 |
| 생성 | 단순 컨텍스트 주입 | 프롬프트 최적화 | 라우팅 + 적응형 생성 | 문서 평가 → 웹 폴백 → 재생성 |
| 한계 | 시멘틱 불일치 높음 | 복잡성 증가 | 설계/디버깅 어려움 | 레이턴시 증가 (200~400ms) |
| 적합 | PoC, 프로토타입 | 프로덕션 | 대규모 엔터프라이즈 | 복잡한 멀티홉 질의 |
⚠️ 최신 업데이트: Agentic RAG가 2025~2026 주요 트렌드. LangGraph 기반 에이전트가 쿼리 분석 → 검색 필요 여부 판단 → 검색 → 문서 평가 → 웹 폴백 → 생성 플로우를 구현. 복잡한 쿼리 처리가 35~50% 향상되나 레이턴시 트레이드오프 존재.
RAG vs Fine-tuning vs Prompt Engineering
| 기준 | RAG | Fine-tuning | Prompt Engineering |
|---|---|---|---|
| 외부 데이터 활용 | O (실시간) | X (학습 시점 고정) | X (프롬프트 내 수동 주입) |
| 최신 정보 반영 | O (DB 업데이트만) | X (재학습 필요) | △ (수동 업데이트) |
| 할루시네이션 | 낮음 (근거 제공) | 중간 | 높음 |
| 비용 | 중간 (임베딩+DB) | 높음 (GPU 학습) | 낮음 |
| 적합 케이스 | 지식 기반 QA | 특정 스타일/도메인 | 간단한 작업 |
실무 적용
# 가장 기본적인 RAG 파이프라인 (LangChain)
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
# 1. 벡터 DB에서 리트리버 생성
vectorstore = Chroma(embedding_function=OpenAIEmbeddings())
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# 2. 프롬프트 템플릿
prompt = ChatPromptTemplate.from_template("""
다음 컨텍스트를 참고하여 질문에 답변하세요.
컨텍스트: {context}
질문: {question}
""")
# 3. RAG 체인 구성
chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| ChatOpenAI(model="gpt-4o") # ⚠️ 최신 업데이트: gpt-4는 레거시, gpt-4o 또는 gpt-4.1 권장
)
# 4. 실행
answer = chain.invoke("RAG에서 임베딩 모델을 교체하면 어떤 문제가 생기나요?")
핵심 체크포인트
- RAG는 컨텍스트를 강화하는 기술이다 (생성이 아님)
- LLM과 리트리버는 완전히 독립적으로 운영 가능
- RAG는 외부 데이터를 실시간으로 활용할 수 있는 유일한 방법
EP2. 임베딩과 질의분해
강의 핵심 요약
임베딩이란?
텍스트를 고정 길이의 숫자 배열(벡터) 로 변환하는 과정:
"안녕하세요" → [토큰 분해] → [어휘 사전] → [벡터 변환]
↓ ↓ ↓
[안, 녕, 하, 세, 요] [137, 122, ...] [0.12, -0.34, 0.56, ...]
(1536차원 등 고정 길이)
임베딩 모델의 세 가지 구성요소: 1. 토크나이저: 텍스트 → 토큰 분해 2. 어휘 사전: 토큰 → 숫자 ID 매핑 3. 임베딩 함수: 가변 길이 숫자열 → 고정 길이 벡터 변환
핵심 제약: 임베딩 모델을 교체하면 전체 RAG DB를 다시 임베딩해야 한다. - 모델마다 토크나이저, 어휘 사전, 임베딩 공식이 다름 - 서로 다른 모델의 임베딩은 호환 불가 - 엔터프라이즈에서는 수십~수백 GB 규모 → 재임베딩 비용 막대 - 결론: 임베딩 모델은 처음부터 신중하게 선택
질의분해 기법
사용자 질의를 여러 개의 팩트 기반 질의로 분해하는 기법.
왜 필요한가? - RAG DB 데이터: 팩트 기반 ("타이레놀 용법은 X이다") - 사용자 질의: 의도 기반 ("삼성 반도체 어떻게 됐어?") - 벡터 유사도가 일치할 확률이 현저히 낮음
핵심 원칙: 질의를 "A는 B이다" 형태로 변환
사용자 질의 (의도 기반):
"삼성이 비메모리에서 뒤쳐지고 있고 샤오미가 AP 발표했는데 대박이다"
❌ 잘못된 분해:
- "삼성전자 최근 비메모리 반도체에서 어떤 성과를 보이고 있나?"
(의문문 → 벡터 유사도 낮음)
✅ 올바른 분해 (팩트 기반):
- "삼성전자 최근 비메모리 반도체 분야의 성과" (A는 B 형태)
- "비메모리 반도체 시장의 삼성 경쟁사 강약 평가 결과"
- "샤오미 최신 AP 칩셋의 성능 벤치마크"
딥다이브: 코사인 유사도
두 벡터 간의 유사도를 측정하는 가장 일반적인 방법:
A · B Σ(Ai × Bi)
cos(θ) = ───────────── = ─────────────────────
|A| × |B| √Σ(Ai²) × √Σ(Bi²)
범위: -1 (완전 반대) ~ 0 (무관) ~ 1 (완전 일치)
주의: "0.7 이상이면 관련성 높음"은 범용 규칙이 아님. 모델과 도메인에 따라 임계값이 크게 달라지므로, 자체 벤치마크로 적절한 임계값을 설정해야 함.
유사도 메트릭 비교 (코사인 vs 내적 vs 유클리드)
| 메트릭 | 수식 | 범위 | 특징 | 사용 시점 |
|---|---|---|---|---|
| 코사인 유사도 | A·B / (|A|×|B|) | -1 ~ 1 | 벡터 크기 무시, 방향만 비교 | 대부분의 RAG (기본 선택) |
| 내적 (Dot Product) | A·B | -∞ ~ +∞ | 크기+방향 모두 반영, 계산 빠름 | 정규화된 임베딩 (코사인과 동일) |
| 유클리드 거리 | √Σ(Ai-Bi)² | 0 ~ +∞ | 절대 거리, 작을수록 유사 | 클러스터링, 이상치 탐지 |
핵심: 대부분의 임베딩 모델(OpenAI, Cohere 등)은 정규화된 벡터를 출력한다. 정규화된 벡터에서는 코사인 유사도 = 내적이므로, 벡터 DB에서 내적을 사용하면 나눗셈을 생략하여 계산이 더 빠르다.
질의분해 프롬프트 예시
# LangChain에서 질의분해 구현
from langchain_core.prompts import ChatPromptTemplate
decompose_prompt = ChatPromptTemplate.from_template("""
당신은 검색 질의 최적화 전문가입니다.
사용자의 질문을 벡터 검색에 최적화된 형태로 분해하세요.
규칙:
1. 각 분해된 질의는 "A는 B이다" 형태의 팩트 문장으로 변환
2. 의문문이 아닌 서술문으로 작성
3. 하나의 질의에 하나의 팩트만 포함
4. 최대 5개까지 분해
사용자 질문: {question}
분해된 질의 (JSON 배열):
""")
핵심 체크포인트
- 임베딩 = 토큰 분해 + 어휘 사전 + 벡터 변환의 결합체
- 임베딩 모델 교체 = 전체 DB 재구축 → 처음에 신중하게 선택
- 질의분해: 사용자 의도 → 팩트 기반 "A는 B" 형태로 변환
EP3. 랭체인, 프롬프트캐쉬, 파이프라이닝
강의 핵심 요약
프롬프트 캐싱
LLM API 제공자의 서버 측 기능. 클라이언트가 제어할 수 없다.
프리픽스 일치 정책: - 프롬프트의 앞부분부터 정확히 일치해야 캐시 적중 - 순서만 바꿔도 캐시 미스 발생 - 시스템 프롬프트를 동일하게 유지하면 캐시 적중률 향상
요청 1: [시스템 프롬프트] + [컨텍스트 A] + [질문 1] → 캐시 저장
요청 2: [시스템 프롬프트] + [컨텍스트 A] + [질문 2] → 시스템+컨텍스트 캐시 적중!
요청 3: [컨텍스트 A] + [시스템 프롬프트] + [질문 3] → 캐시 미스 (순서 다름)
랭체인의 본질 = 파이프라이닝 라이브러리
핵심 개념: Runnable
- C#의 Task, JavaScript의 Promise와 유사한 개념
- 입력 타입과 출력 타입이 정의된 실행 단위
- 타입이 호환되면 | 연산자로 체이닝 가능
파이프라이닝 = 함수 합성의 연쇄 (Function Composition)
f: int → string (숫자를 문자열로)
g: string → string (문자열 변환)
h: string → bool (문자열을 판별)
f | g | h → int → bool (자동 체이닝)
랭체인의 가치: - LLM 관련 사전 정의된 Runnable을 대량 제공 - 프롬프트 템플릿, 파서, 리트리버 등이 모두 Runnable - 복잡한 체이닝과 실험을 간결하게 표현 가능
딥다이브: LangChain LCEL (LangChain Expression Language)
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
# 각각이 Runnable: 입력→출력 타입이 정의됨
prompt = ChatPromptTemplate.from_template("'{topic}'에 대해 설명해줘") # dict → PromptValue
model = ChatOpenAI(model="gpt-4o") # PromptValue → AIMessage # ⚠️ 최신 업데이트: gpt-4.1도 사용 가능
parser = StrOutputParser() # AIMessage → str
# LCEL 파이프라이닝: | 연산자로 체이닝
chain = prompt | model | parser # dict → str
# 실행
result = chain.invoke({"topic": "RAG"})
# 스트리밍도 동일한 체인으로
for chunk in chain.stream({"topic": "RAG"}):
print(chunk, end="")
# 배치 처리도 동일
results = chain.batch([
{"topic": "RAG"},
{"topic": "Fine-tuning"},
{"topic": "Prompt Engineering"}
])
프롬프트 캐싱 비용 절감 사례
⚠️ 최신 업데이트 (2026.03 기준) — Anthropic Claude 프롬프트 캐싱 가격:
모델별 기본 입력 가격이 다르며, 캐싱은 배수(multiplier) 기반으로 계산:
- 캐시 읽기(적중): 기본 가격의 0.1배 (90% 절감!)
- 캐시 쓰기(5분): 기본 가격의 1.25배
- 캐시 쓰기(1시간): 기본 가격의 2배
| 모델 | 기본 입력 가격 | 캐시 읽기 | 캐시 쓰기(5분) |
|------|--------------|----------|---------------|
| Haiku 4.5 | $1/1M | $0.10/1M | $1.25/1M |
| Sonnet 4.5 | $3/1M | $0.30/1M | $3.75/1M |
| Opus 4.5 | $5/1M | $0.50/1M | $6.25/1M |
전략: 시스템 프롬프트 + 정적 컨텍스트를 앞에 고정
→ 반복 호출 시 캐시 적중률 극대화
핵심 체크포인트
- 프롬프트 캐싱은 서버 측 기능, 프리픽스 순서 유지가 핵심
- 랭체인 = 파이프라이닝 라이브러리 (Runnable 체인)
- LCEL의
|연산자 = 함수 합성, 타입 호환되면 자동 연결
⚠️ 최신 업데이트: LCEL의 한계와 LangGraph LCEL은 선형 체인에 강하지만, 조건 분기·루프·재시도가 필요한 복잡한 워크플로우에서는 한계가 있다. LangChain 1.0(2025.10) 이후, 에이전트 개발은 LangGraph 중심으로 재편되었으며
AgentExecutor는 공식 deprecated. LangChain의 역할은 "파이프라이닝 라이브러리"에서 "에이전트 프레임워크의 기반 레이어"로 진화 중.```python
LangGraph 기반 에이전트 예시 (LCEL 대비 조건 분기 가능)
from langgraph.graph import StateGraph graph = StateGraph(AgentState) graph.add_node("retrieve", retrieve_docs) graph.add_node("grade", grade_documents) # 문서 품질 평가 graph.add_node("generate", generate_answer) graph.add_conditional_edges("grade", decide_to_generate, {"useful": "generate", "not_useful": "retrieve"}) # 조건 분기 ```
EP4. 프롬프트 기법
강의 핵심 요약
프롬프트의 5가지 구성요소
| 요소 | 역할 | 예시 |
|---|---|---|
| Instruction | 역할과 수행 방법 정의 | "당신은 의학 전문가입니다" |
| Context | 참고할 추가 정보 | RAG로 가져온 관련 문서 |
| Example | 입출력 예시 (Few-shot) | "Q: ... A: ..." 쌍 |
| Input Format | 입력 데이터 형식 | "JSON 형식으로 제공됩니다" |
| Output Format | 출력 형식 지정 | "마크다운 표로 출력하세요" |
주요 프롬프트 기법
Zero-shot: 예제 없이 지시만으로 수행
질문: 다음 문장의 감정을 분석하세요.
"오늘 날씨가 정말 좋다" → 긍정
Few-shot: 여러 예제를 제공하여 패턴 학습 유도
예시 1: "배가 아프다" → 부정
예시 2: "꽃이 예쁘다" → 긍정
예시 3: "비가 온다" → 중립
질문: "시험에 떨어졌다" → ?
Chain-of-Thought (CoT): 단계별 사고 과정을 유도
문제: 가게에 사과 23개가 있었고, 5개를 팔았고, 8개를 새로 받았다.
지금 사과는 몇 개인가?
"단계별로 생각해 봅시다" ← 이 한 마디가 핵심
1단계: 시작 사과 = 23개
2단계: 판매 후 = 23 - 5 = 18개
3단계: 입고 후 = 18 + 8 = 26개
답: 26개
딥다이브: 프롬프트 5요소 실전 템플릿
# RAG 시스템의 실전 프롬프트 (5요소 모두 포함)
RAG_PROMPT = """
# Instruction (역할 + 규칙)
당신은 {company}의 고객 지원 전문가입니다.
반드시 제공된 컨텍스트 내의 정보만 사용하여 답변하세요.
컨텍스트에 없는 정보는 "확인이 어렵습니다"라고 답하세요.
# Context (RAG 검색 결과)
다음은 관련 문서입니다:
---
{context}
---
# Example (Few-shot)
예시 질문: "반품 기한이 어떻게 되나요?"
예시 답변: "구매일로부터 14일 이내에 반품이 가능합니다. (출처: 반품 정책 문서)"
# Input Format
고객 질문: {question}
# Output Format
다음 형식으로 답변하세요:
- 답변: (1-3문장)
- 출처: (참고한 문서명)
- 신뢰도: (높음/중간/낮음)
"""
CoT vs 일반 프롬프트 비교
# 일반 프롬프트
"이 코드의 버그를 찾아주세요: {code}"
→ 종종 표면적인 답변만 제공
# CoT 프롬프트
"이 코드를 분석하겠습니다. 단계별로 살펴봅시다:
1. 먼저 입력값의 범위를 확인합니다
2. 각 조건분기의 경계값을 테스트합니다
3. 메모리/리소스 관련 이슈를 검토합니다
코드: {code}"
→ 체계적이고 깊이 있는 분석
⚠️ 최신 업데이트: CoT 이후 고급 추론 기법들
기법 핵심 아이디어 CoT 대비 장점 트레이드오프 Tree-of-Thought (ToT) 여러 추론 경로를 트리로 탐색, 최적 선택 복잡 추론 정확도 20~25% 향상 LLM 호출 횟수 증가 (비용↑) Graph-of-Thought (GoT) 추론 단계를 그래프로 모델링, 병합/분기 비선형 문제에 강함 구현 복잡도 높음 Self-Consistency 같은 질문을 N번 반복, 다수결 투표 단일 CoT 대비 안정적 N배의 비용 RAG 파이프라인에서는 질의분해에 ToT, 답변 검증에 Self-Consistency를 적용하면 효과적.
핵심 체크포인트
- 프롬프트 5요소: Instruction, Context, Example, Input Format, Output Format
- Few-shot으로 위험한 응답을 길들일 수 있다 (편향 유도)
- CoT = "단계별로 생각하자" 한 마디로 추론 능력 극대화
EP5. 모델과 튜닝
강의 핵심 요약
모델 분류 체계
[Pretrained/Base Model] → 범용 학습 (인터넷 텍스트)
↓
[Supervised Fine-Tuned (SFT)] → 특정 목적 지도 학습
↓
[RLHF / DPO] → 인간 선호도 학습 (안전성, 유용성)
↓
[Mixture of Experts (MoE)] → 전문 분야별 라우팅
| 단계 | 특징 | 예시 |
|---|---|---|
| Base | 편향 없음, 전문성 없음 | Llama-3-base |
| SFT | 특정 태스크에 특화 | Llama-3-chat |
| RLHF | 안전하고 유용한 답변 | ChatGPT, Claude |
| MoE | 여러 전문가 모델 조합 | Mixtral 8x7B |
경량 튜닝: LoRA & QLoRA
LoRA (Low-Rank Adaptation) - 전체 모델 파라미터를 학습하지 않고, 특정 레이어의 작은 행렬만 추가 학습 - 원본 모델 가중치는 동결(freeze) - 학습 파라미터 수를 1% 미만으로 줄임
원본 모델: W (거대 행렬, 예: 4096×4096)
LoRA: W + ΔW, 여기서 ΔW = A × B (A: 4096×r, B: r×4096, r=8~64)
r=8이면: 학습 파라미터 = 4096×8 + 8×4096 = 65,536개
원본: 학습 파라미터 = 4096×4096 = 16,777,216개
→ 99.6% 절감!
QLoRA: 4비트 양자화 + LoRA - 모델 가중치를 4비트로 압축 후 LoRA 적용 - 개인 GPU (24GB)에서도 7B 모델 학습 가능
⚠️ 최신 업데이트: LoRA 이후 주요 변형 기법들 (2024~2025)
기법 핵심 아이디어 LoRA 대비 장점 DoRA 가중치를 크기(magnitude)와 방향(direction)으로 분해하여 별도 학습 Full fine-tuning에 더 근접한 성능 QDoRA QLoRA + DoRA 결합 QLoRA를 일관되게 능가 rsLoRA rank에 따른 학습률 스케일링 자동 조정 높은 rank에서 안정적 학습 AdaLoRA 중요 레이어에 자동으로 더 높은 rank 할당 파라미터 예산 최적 배분 실무 선택 기준: 빠른 실험 → LoRA, 품질 극대화 → DoRA, 메모리 제한 → QLoRA/QDoRA
# PEFT 라이브러리로 LoRA 적용 실전 코드
from peft import LoraConfig, get_peft_model, TaskType
config = LoraConfig(
r=16, # rank: 8~64 (높을수록 표현력↑, 메모리↑)
lora_alpha=32, # 스케일링: 보통 r의 2배
target_modules=["q_proj", "v_proj"], # Attention Q, V에 적용 (가성비 최고)
lora_dropout=0.05,
task_type=TaskType.CAUSAL_LM,
)
model = get_peft_model(base_model, config)
model.print_trainable_parameters()
# → "trainable params: 0.06% of 7B"
데이터 가공 핵심
팩트 데이터를 질문-답변 쌍(Completion) 으로 변환:
원본 팩트: "타이레놀의 1회 복용량은 500mg이다"
변환 결과 (SFT 학습 데이터):
{
"instruction": "타이레놀의 1회 복용량은 얼마인가요?",
"output": "타이레놀의 1회 복용량은 500mg입니다."
}
Instruction Alignment (다양한 질문 형태):
- "타이레놀 용량 알려줘" → 같은 답변
- "Tylenol 얼마나 먹어야 해?" → 같은 답변
- "진통제 복용량이 궁금합니다" → 같은 답변
모델 선택 실무 기준
| 용도 | 추천 | 이유 |
|---|---|---|
| 데이터 가공/변환 | 저비용 모델 (Haiku 4.5, GPT-4.1 mini) | 단순 작업, 비용 효율 |
| 임베딩 생성 | 전용 임베딩 모델 | LLM 모델과 별개 |
| 최종 답변 생성 | 고성능 모델 (Claude Sonnet 4.5, GPT-4.1) | 품질이 중요 |
| RAG 파이프라인 내부 | 저비용 모델 | 질의분해, 리랭킹 등 |
⚠️ 최신 업데이트: GPT-4o-mini → GPT-4.1 mini (2025.04), GPT-4 → GPT-4.1. GPT-4o는 2026.02 retire 예정.
딥다이브: Hugging Face에서 모델 선택 기준
1. MTEB 리더보드 확인 (임베딩 모델)
→ https://huggingface.co/spaces/mteb/leaderboard
2. 체크 항목:
- 모델 크기 (파라미터 수) → 인프라 요구사항
- 지원 언어 (한국어 포함 여부)
- 최대 시퀀스 길이 (512 vs 8192 토큰)
- 임베딩 차원 (768 vs 1536 vs 3072)
- 라이선스 (상업적 사용 가능 여부)
3. 한국어 RAG 추천:
- multilingual-e5-large (다국어, 1024차원)
- bge-m3 (다국어, 1024차원, 희소+밀집 동시)
- KoSimCSE (한국어 특화)
핵심 체크포인트
- Base → SFT → RLHF 순서로 모델이 점점 전문화
- LoRA = 원본 동결 + 작은 행렬 추가 학습 (99% 파라미터 절감)
- 비싼 모델은 최종 답변에만, 내부 작업은 저비용 모델 활용
EP6. 컨텍스트와 외부 데이터 로딩
강의 핵심 요약
컨텍스트의 세 가지 과제
- 용량: 컨텍스트가 많을수록 → 비용 증가 (토큰 과금, 처리 속도 저하)
- 주입 방식: 데이터를 프롬프트에 어떻게 넣을 것인가
- 압축: 대량 데이터를 제한된 컨텍스트 윈도우 내에 포함
컨텍스트 압축 전략
| 방법 | 설명 | 장단점 |
|---|---|---|
| LLM 요약 | LLM으로 문서를 요약 후 주입 | 정보 손실 위험 |
| 개별 정제 | 각 문서를 핵심만 추출 | 노가다이지만 정확 |
| 그래프 변환 | 문서를 지식 그래프로 변환 | LLM 최적화, 구축 비용 높음 |
외부 데이터 로딩의 쟁점
- 비텍스트: 이미지, PDF(표/그림), 사운드 → 텍스트 변환 필요
- 포맷 다양성: JSON, XML, HTML, CSV → 통일된 형태로 파싱
- I/O 처리: 웹 크롤링, S3, 구글 드라이브 등 소스별 커넥터
- 데이터 정합성: 깨진 인코딩, 중복, 누락 검사
청킹 (Chunking) — 가장 중요한 실무 노하우
"분할 목적을 먼저 명확히 하라" ← 가장 중요한 원칙
딥다이브: 청킹 전략 비교
| 전략 | 방식 | 장점 | 단점 | 적합 |
|---|---|---|---|---|
| Fixed Size | 글자/토큰 수로 고정 분할 | 구현 간단 | 문맥 파괴 | 균일한 텍스트 |
| Recursive | 구분자 우선순위로 분할 (\n\n → \n → . → " ") | 문맥 보존 | 크기 불균일 | 일반 문서 |
| Semantic | 임베딩 유사도로 의미 단위 분할 | 이론상 의미 보존 유리 | 느림, 비용 높음, 벤치마크에서 Recursive 대비 일관된 우위 없음 | 긴 논문/보고서 |
| Agentic | LLM이 직접 분할 판단 | 도메인 특화 데이터에서 효과적 | 매우 느림/비쌈, 범용 벤치마크에서는 결과 혼재 | 고가치 데이터 |
# LangChain Recursive Character Text Splitter
from langchain_text_splitters import RecursiveCharacterTextSplitter # ⚠️ 최신 업데이트: langchain.text_splitter → langchain_text_splitters 패키지로 분리
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 청크 최대 크기
chunk_overlap=200, # 청크 간 겹침 (문맥 연결)
separators=["\n\n", "\n", ". ", " ", ""], # 우선순위
)
chunks = splitter.split_text(long_document)
# 결과: ["첫 번째 청크...", "두 번째 청크...", ...]
청킹 설계 질문 체크리스트
- 이 데이터의 정보 단위는 무엇인가? (문단? 섹션? 문장?)
- 검색 시 기대하는 결과의 크기는? (짧은 답변? 긴 설명?)
- 메타데이터를 활용할 수 있는가? (제목, 날짜, 카테고리)
- 청크 간 문맥 연결이 필요한가? (overlap 크기 결정)
핵심 체크포인트
- 컨텍스트 용량 = 비용과 직결 → 필요한 만큼만 주입
- 청킹 전략: 분할 목적을 먼저 정의한 후 방법 선택
- 실무에서 청킹은 "노가다" 영역 → 반복 실험 필수
EP7. LLM 임베딩 vs RAG 임베딩
강의 핵심 요약
LLM의 임베딩
- 각 토큰이 개별 벡터를 가짐
- 문맥 위치 고려, 다음 토큰 예측 최적화
- 차원이 매우 큼 (8,000~11,500+)
- 컨텍스트, 위치, 의미 정보를 모두 포함
입력: "나는 학교에 간다"
LLM 내부:
"나는" → [0.12, -0.34, ..., 0.56] (8192차원)
"학교에" → [0.78, 0.23, ..., -0.12] (8192차원)
"간다" → [0.45, -0.67, ..., 0.89] (8192차원)
→ 각 토큰별 독립 벡터, 총 3개
RAG의 임베딩
- 풀링(Pooling) 으로 모든 토큰 벡터를 하나로 결합
- 의미론적 유사도만 최적화
- 차원이 상대적으로 작음 (LLM의 절반 수준)
입력: "나는 학교에 간다"
RAG 임베딩 모델:
토큰별 벡터 → [Pooling] → [0.34, -0.12, ..., 0.67] (1536차원)
→ 전체 문장을 대표하는 단일 벡터
풀링 기법 비교
| 풀링 | 방식 | 특징 |
|---|---|---|
| Mean Pooling | 모든 토큰 벡터의 산술 평균 | 가장 일반적, 안정적 |
| Max Pooling | 각 차원의 최댓값 선택 | 핵심 특징 강조 |
| Attention-Weighted | 중요도 가중치 기반 결합 | 가장 정교, 학습 필요 |
토큰 벡터:
"나는" → [0.1, 0.3, 0.5]
"학교에" → [0.4, 0.2, 0.8]
"간다" → [0.3, 0.7, 0.2]
Mean Pooling: [(0.1+0.4+0.3)/3, (0.3+0.2+0.7)/3, (0.5+0.8+0.2)/3]
= [0.267, 0.400, 0.500]
Max Pooling: [max(0.1,0.4,0.3), max(0.3,0.2,0.7), max(0.5,0.8,0.2)]
= [0.4, 0.7, 0.8]
딥다이브: 왜 RAG 임베딩은 별도 학습이 필요한가?
LLM 임베딩은 RAG에 적합하지 않다: 1. LLM은 "다음 토큰 예측"에 최적화 → 문장 전체의 의미 표현에 부적합 2. LLM 벡터는 위치/문맥 정보 포함 → 순수한 "의미 유사도"가 아님 3. 차원이 너무 큼 → 검색 비용/속도에 불리
RAG 임베딩 모델의 학습 방식:
Contrastive Learning (대조 학습)
긍정 쌍: ("파이썬 리스트 정렬", "Python에서 list를 sort하는 방법") → 유사도 ↑
부정 쌍: ("파이썬 리스트 정렬", "자바스크립트 DOM 조작") → 유사도 ↓
수천만 쌍으로 학습 → 의미적으로 유사한 문장의 벡터가 가까워짐
⚠️ 최신 업데이트: 임베딩 표현 방식의 스펙트럼
LLM 임베딩(토큰별)과 RAG 임베딩(단일 벡터) 사이에 중간 방식들이 존재:
방식 벡터 수 대표 모델 장점 단점 Single-vector 문장당 1개 BERT, E5, BGE 빠른 검색, 인덱싱 효율 세밀한 매칭 어려움 Multi-vector (Late Interaction) 토큰당 1개 ColBERT, bge-m3 토큰 레벨 정밀 매칭 저장 공간 ↑, 인덱싱 복잡 Cross-encoder 없음 (점수만) ms-marco-MiniLM 최고 정확도 검색 불가, 리랭킹만 가능 Matryoshka Representation Learning (MRL): 하나의 모델에서 차원을 유연하게 축소 가능 (예: 3072 → 1024 → 256). OpenAI text-embedding-3, Cohere embed-v4 등이 지원. 저장 비용과 검색 속도를 유연하게 조절할 수 있다.
핵심 체크포인트
- LLM 임베딩: 토큰별 벡터, 컨텍스트/위치 포함, 고차원
- RAG 임베딩: 풀링으로 문장 단일 벡터, 의미 유사도 특화, 저차원
- RAG에는 전용 임베딩 모델이 필수 (LLM 임베딩 재활용 불가)
EP8. 임베딩용 모델
강의 핵심 요약
임베딩 모델 선택 기준 두 가지
1. 청크 크기 (Chunk Size) 호환성 - 모델이 학습할 때 사용한 청크 크기와 실제 사용 청크 크기가 일치해야 최적 성능 - 100K 토큰 학습 모델에 10개 토큰을 넣으면 → 성능 저하 - 모델의 학습 데이터셋/논문에서 청크 크기 확인 필수
2. 학습 도메인 - 범용 모델 vs 도메인 특화 모델 - 사용자 질의와 학습 텍스트의 스타일이 일치해야 유사도 정확
| 데이터 도메인 | 추천 접근 | 예시 |
|---|---|---|
| 일반 텍스트 | 범용 모델 | E5, BGE |
| 의학 데이터 | 의학 특화 모델 | PubMedBERT 기반 |
| 법률 문서 | 법률 특화 모델 | Legal-BERT 기반 |
| 코드 | 코드 특화 모델 | CodeBERT, Voyage-code |
딥다이브: MTEB 리더보드 활용법
MTEB (Massive Text Embedding Benchmark): 임베딩 모델 성능 비교 표준
확인할 메트릭:
1. Retrieval (검색): RAG에 가장 중요
2. STS (Semantic Textual Similarity): 의미 유사도
3. Classification: 텍스트 분류
4. Clustering: 문서 클러스터링
한국어 RAG 실무 추천 모델 (⚠️ 2026.03 기준으로 업데이트):
┌─────────────────────────────────────────────────────┐
│ 모델 │ 차원 │ 한국어 │ 특징 │
├─────────────────────────────────────────────────────┤
│ bge-m3 │ 1024 │ O │ 희소+밀집 동시 │
│ multilingual-e5-large│ 1024 │ O │ 다국어 범용 │
│ text-embedding-3-large│3072 │ O │ OpenAI API │
│ voyage-3-large │ 1024 │ O │ Voyage AI API │
│ embed-v4 │ 1024 │ O │ Cohere API │
│ Qwen3-Embedding │ 다양 │ O │ 오픈소스, MTEB 상위│
└─────────────────────────────────────────────────────┘
⚠️ 최신 업데이트: voyage-multilingual-2 → voyage-3-large,
embed-multilingual-v3 → embed-v4 (128K토큰, 멀티모달 지원)
도메인별 모델 선택 의사결정 트리
데이터가 어떤 도메인인가?
├── 범용 (뉴스, 위키, 일반 문서)
│ └── 다국어 필요?
│ ├── Yes → bge-m3 또는 multilingual-e5
│ └── No (영어만) → gte-large 또는 bge-large-en
├── 전문 도메인 (의학, 법률, 금융)
│ └── 도메인 특화 모델 존재?
│ ├── Yes → 도메인 모델 사용 (예: PubMedBERT)
│ └── No → 범용 모델 + 도메인 데이터 Fine-tune
└── 코드
└── Voyage-code 또는 CodeBERT 계열
임베딩 생성 코드 예시
# 1. 오픈소스 모델 (sentence-transformers)
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("BAAI/bge-m3")
embeddings = model.encode(["RAG란 무엇인가?", "임베딩 모델 비교"])
print(embeddings.shape) # (2, 1024)
# 2. OpenAI API
from openai import OpenAI
client = OpenAI()
response = client.embeddings.create(
model="text-embedding-3-large",
input=["RAG란 무엇인가?"],
dimensions=1024, # MRL 지원: 차원 축소 가능 (비용/성능 트레이드오프)
)
embedding = response.data[0].embedding
핵심 체크포인트
- 임베딩 모델 선택 = 청크 크기 호환 + 도메인 일치
- MTEB 리더보드에서 Retrieval 메트릭 중심으로 비교
- 한 번 선택하면 교체 어려움 → 충분한 벤치마크 후 결정
EP9. 희소벡터와 밀집벡터
강의 핵심 요약
희소 벡터 (Sparse Vector)
대표 알고리즘: BM25 (TF-IDF의 개선 버전)
원리: 단어 빈도 기반 검색 - 토큰 ID = 벡터의 차원 - 토큰 빈도 = 해당 차원의 값 - 대부분의 차원이 0 → "희소"
어휘 사전: {"사과": 0, "바나나": 1, "딸기": 2, "포도": 3, ...} (10만 단어)
문서: "사과 사과 바나나"
희소 벡터: [2, 1, 0, 0, ..., 0] (10만 차원, 대부분 0)
↑ ↑
사과2회 바나나1회
| 장점 | 단점 |
|---|---|
| CPU만으로 충분 | 의미 이해 불가 ("강아지" ≠ "puppy") |
| 매우 빠름 | 새 토큰 추가 시 전체 인덱스 재구축 |
| 저비용 | 토큰 증가 시 차원 폭발 |
| 정확한 키워드 매칭 | 오타에 취약 |
밀집 벡터 (Dense Vector)
신경망으로 학습된 벡터 — 모든 차원이 의미를 가짐
"강아지가 공원에서 뛰어놀았다"
밀집 벡터: [0.12, -0.34, 0.56, 0.78, ...] (768~3072차원, 0이 거의 없음)
| 장점 | 단점 |
|---|---|
| 의미론적 검색 가능 | GPU 필요, 비용 높음 |
| 오타/변형된 단어 처리 | 시멘틱 불일치 가능 |
| 동의어/유사어 이해 | 정확한 키워드 매칭 약함 |
시멘틱 불일치 해결 3가지 방법
- 데이터 변형: DB에 저장할 데이터 구조를 검색 친화적으로 변경
- 질의 변형: 사용자 질의를 모델 학습 구조에 맞게 변환 (=질의분해)
- 모델 변경: 도메인에 맞는 다른 임베딩 모델로 교체
RAG 검색 개선 기법 총정리
| 기법 | 설명 | 효과 |
|---|---|---|
| 질의분해 | 의도 → 팩트 기반 질의 변환 | 벡터 유사도 향상 |
| Multi-hop | 데이터를 여러 관점으로 벡터화 | 검색 다양성 확보 |
| 하이브리드 검색 | 희소 + 밀집 결합 | 키워드+의미 동시 활용 |
| 메타데이터 필터 | 날짜, 카테고리 등 필터 조건 | 검색 범위 축소 |
| 리랭킹 | 1차 검색 후 정밀 재순위화 (Cohere Rerank 3.5, cross-encoder, Mixedbread AI 등) | 최종 품질 향상 |
딥다이브: BM25 수식과 직관
(k₁ + 1) × tf(t,d)
BM25(t,d) = IDF(t) × ─────────────────────────────────────
k₁ × (1 - b + b × |d| / avgdl) + tf(t,d)
IDF(t) = log((N - df(t) + 0.5) / (df(t) + 0.5) + 1)
용어:
- tf(t,d): 문서 d에서 토큰 t의 출현 빈도
- IDF(t): 역문서 빈도 (희귀한 단어일수록 높은 점수)
- |d|: 문서 길이, avgdl: 평균 문서 길이
- k₁: 빈도 포화 파라미터 (보통 1.2~2.0)
- b: 문서 길이 정규화 파라미터 (보통 0.75)
직관적 이해: - 희귀한 단어가 포함되면 점수 ↑ (IDF) - 같은 단어가 많이 나오면 점수 ↑ (TF), 하지만 포화됨 (k₁) - 짧은 문서에서 나온 키워드가 더 의미 있음 (b)
⚠️ 최신 업데이트: BM25 변종 — BM25+, BM25L은 짧은 문서/청크에 대한 편향을 개선. RAG에서 청크가 짧은 경우 BM25+를 사용하면 짧은 청크의 점수가 과도하게 낮아지는 문제를 방지한다.
하이브리드 검색 구현
# 하이브리드 검색: BM25(희소) + Dense(밀집) 결합
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
from langchain_community.vectorstores import Chroma
# 희소 벡터 검색 (BM25)
bm25_retriever = BM25Retriever.from_documents(documents)
bm25_retriever.k = 5
# 밀집 벡터 검색
chroma_retriever = Chroma.from_documents(
documents, embedding=OpenAIEmbeddings()
).as_retriever(search_kwargs={"k": 5})
# 앙상블 (Reciprocal Rank Fusion)
hybrid_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, chroma_retriever],
weights=[0.4, 0.6] # BM25 40% + Dense 60%
)
results = hybrid_retriever.invoke("RAG 임베딩 모델 선택 기준")
Reciprocal Rank Fusion (RRF) — 하이브리드 검색의 핵심
1
RRF(d) = Σ ─────────
k + rank(d)
k = 60 (상수, 보통 60)
rank(d) = 해당 검색기에서의 순위
예: 문서 A가 BM25에서 1위, Dense에서 3위
RRF(A) = 1/(60+1) + 1/(60+3) = 0.0164 + 0.0159 = 0.0323
예: 문서 B가 BM25에서 5위, Dense에서 1위
RRF(B) = 1/(60+5) + 1/(60+1) = 0.0154 + 0.0164 = 0.0318
→ 문서 A가 두 검색기 모두에서 상위 → 최종 순위 더 높음
핵심 체크포인트
- 희소 벡터 (BM25): 빠르고 저렴, 키워드 정확 매칭, 의미 이해 불가
- 밀집 벡터: 의미 검색 가능, 비용 높음, 시멘틱 불일치 위험
- 하이브리드 검색: 둘의 장점을 결합 → 실무에서 가장 추천되는 방식
부록 A: 용어 사전
| 용어 | 설명 |
|---|---|
| RAG | Retrieval-Augmented Generation. 검색으로 컨텍스트를 강화하여 LLM 답변 품질을 높이는 기법 |
| 임베딩 (Embedding) | 텍스트를 고정 길이 벡터(숫자 배열)로 변환하는 과정 |
| 벡터 DB | 벡터 데이터를 저장하고 유사도 검색을 수행하는 데이터베이스 (Chroma, Pinecone, Weaviate 등) |
| 토크나이저 | 텍스트를 토큰 단위로 분해하는 모듈 |
| 어휘 사전 (Vocabulary) | 토큰과 숫자 ID의 매핑 테이블 |
| 코사인 유사도 | 두 벡터 간의 방향 유사도를 -1~1로 측정하는 메트릭 |
| 청킹 (Chunking) | 긴 문서를 검색에 적합한 단위로 분할하는 과정 |
| 리트리버 (Retriever) | 벡터 DB에서 관련 문서를 검색하는 컴포넌트 |
| 리랭킹 (Re-ranking) | 1차 검색 결과를 정밀 모델로 재순위화하는 과정 |
| 질의분해 | 복합 질의를 여러 개의 단순 팩트 기반 질의로 분해하는 기법 |
| 풀링 (Pooling) | 여러 토큰 벡터를 하나의 문장 벡터로 결합하는 방법 |
| BM25 | 단어 빈도 기반 검색 알고리즘 (희소 벡터) |
| LoRA | Low-Rank Adaptation. 소수 파라미터만 학습하는 경량 튜닝 기법 |
| QLoRA | 4비트 양자화 + LoRA. 적은 GPU 메모리로 모델 튜닝 |
| SFT | Supervised Fine-Tuning. 질문-답변 쌍으로 지도 학습 |
| RLHF | Reinforcement Learning from Human Feedback. 인간 피드백 기반 강화학습 |
| MoE | Mixture of Experts. 전문가 모델을 조합하는 아키텍처 |
| LCEL | LangChain Expression Language. 랭체인의 파이프라이닝 문법 |
| Runnable | 랭체인에서 입력→출력이 정의된 실행 단위 |
| 프롬프트 캐싱 | 반복되는 프롬프트 프리픽스를 서버에서 캐시하여 비용 절감 |
| CoT | Chain-of-Thought. 단계별 추론을 유도하는 프롬프트 기법 |
| MTEB | Massive Text Embedding Benchmark. 임베딩 모델 성능 비교 벤치마크 |
| RRF | Reciprocal Rank Fusion. 여러 검색 결과를 병합하는 알고리즘 |
| 하이브리드 검색 | 희소 벡터(BM25) + 밀집 벡터를 결합하는 검색 방식 |
| Contrastive Learning | 유사 쌍은 가깝게, 비유사 쌍은 멀게 학습하는 기법 |
| ColBERT | Late Interaction 기반 multi-vector 검색 모델. 토큰 레벨 정밀 매칭으로 단일 벡터 대비 높은 정확도 |
| MRL | Matryoshka Representation Learning. 하나의 임베딩에서 차원을 유연하게 축소(3072→1024→256)하여 비용/성능 트레이드오프 조절 |
| Guardrails | LLM 출력의 안전성·형식·정합성을 검증하는 프레임워크. RAG 파이프라인에서 할루시네이션 방지에 활용 |
| DoRA | Direction-Magnitude Decomposition LoRA. LoRA보다 full fine-tuning에 근접한 성능 |
| LangGraph | LangChain 생태계의 에이전트 프레임워크. 조건 분기·루프·상태 관리가 가능한 그래프 기반 워크플로우 |
| Agentic RAG | 에이전트가 검색 필요 여부를 판단하고, 문서 평가·웹 폴백까지 수행하는 자율형 RAG 패턴 |
부록 B: 임베딩 모델 비교표
⚠️ 최신 업데이트 (2026.03 기준으로 갱신)
| 모델 | 차원 | 최대 토큰 | 한국어 | 유형 | 라이선스 |
|---|---|---|---|---|---|
| text-embedding-3-large | 3072 | 8191 | O | API | 유료 |
| text-embedding-3-small | 1536 | 8191 | O | API | 유료 |
| voyage-3-large | 1024 | 32000 | O | API | 유료 |
| embed-v4 | 1024 | 128000 | O | API | 유료 |
| bge-m3 | 1024 | 8192 | O | 오픈소스 | MIT |
| multilingual-e5-large | 1024 | 512 | O | 오픈소스 | MIT |
| gte-Qwen2 | 다양 | 8192 | O | 오픈소스 | Apache 2.0 |
| Qwen3-Embedding | 다양 | 8192 | O | 오픈소스 | Apache 2.0 |
| KoSimCSE-roberta | 768 | 512 | O | 오픈소스 | MIT |
| bge-large-en | 1024 | 512 | X | 오픈소스 | MIT |
부록 C: 추천 참고 자료
공식 문서
벤치마크 & 리더보드
논문
- "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks" (2020, Lewis et al.)
- "LoRA: Low-Rank Adaptation of Large Language Models" (2021, Hu et al.)
- "BERT: Pre-training of Deep Bidirectional Transformers" (2018, Devlin et al.)
- "Okapi BM25: A Non-Binary Model" (1994, Robertson et al.)
- "Reciprocal Rank Fusion" (2009, Cormack et al.)
실습 리포지토리
클릭하거나 Space를 눌러 뒤집기